use craftdig_math::{Direction, AABB};

/// Describes information used to instantiate a block in a chunk, including the
/// kind of mesh and its bounding boxes.
#[derive(Clone, Debug)]
pub struct BlockModel {
    /// If the block is solid or what.
    solidity: Solidity,

    /// Size of the selection bounding box.
    ///
    /// Not yet implemented.
    selection: AABB,

    /// Collision shape of the block.
    collider: Collider,

    /// Rendered mesh shape.
    shape: Shape,

    /// Color that can be used in unusual circumstances.
    fallback_color: Color,
}

impl BlockModel {
    pub fn new(
        solidity: Solidity,
        selection: AABB,
        collider: Collider,
        shape: Shape,
        fallback_color: Color,
    ) -> Self {
        Self {
            solidity,
            selection,
            collider,
            shape,
            fallback_color,
        }
    }

    pub fn new_simple(textbl: TexTable) -> Self {
        Self::new(
            Solidity::Opaque,
            AABB::unit(),
            Collider::full_cube(),
            Shape::full_cube(textbl),
            Color::from((255, 255, 255)),
        )
    }

    pub fn new_empty() -> Self {
        Self::new(
            Solidity::Empty,
            AABB::unit(),
            Collider::full_cube(),
            Shape::full_cube(TexTable::new_simple(0)),
            Color::from((255, 255, 255)),
        )
    }

    pub fn solidity(&self) -> Solidity {
        self.solidity
    }

    pub fn selection(&self) -> &AABB {
        &self.selection
    }

    pub fn collider(&self) -> &Collider {
        &self.collider
    }

    pub fn shape(&self) -> &Shape {
        &self.shape
    }

    pub fn fallback_color(&self) -> Color {
        self.fallback_color
    }
}

/// Describes how the neighbors of a block can be visible and how light
/// propagates through a block.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Solidity {
    Opaque,
    Empty,
    Transparent,
}

/// Color in 0xRRGGBB format.  This is fallback if we can't or don't want to
/// load a primary texture for whatever reason.
#[derive(Copy, Clone, Debug)]
pub struct Color(u32);

impl Into<(u8, u8, u8)> for Color {
    fn into(self) -> (u8, u8, u8) {
        let Self(v) = self;
        (
            (v >> 16 & 0xff) as u8,
            (v >> 8 & 0xff) as u8,
            (v & 0xff) as u8,
        )
    }
}

impl From<(u8, u8, u8)> for Color {
    fn from((r, g, b): (u8, u8, u8)) -> Self {
        Self((r as u32) << 16 | (g as u32) << 8 | b as u32)
    }
}

#[derive(Clone, Debug)]
pub enum Shape {
    Cubic { aabb: AABB, texes: TexTable },
}

impl Shape {
    pub fn full_cube(texes: TexTable) -> Self {
        Self::Cubic {
            aabb: AABB::unit(),
            texes,
        }
    }
}

/// Represents the collision shape for a block.
#[derive(Clone, Debug)]
pub enum Collider {
    Empty,
    Cubic(AABB),
}

impl Collider {
    pub fn full_cube() -> Self {
        Self::Cubic(AABB::unit())
    }
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct TexTable {
    pub indexes: [u8; 6],
}

impl TexTable {
    pub fn new(indexes: [u8; 6]) -> Self {
        Self { indexes }
    }

    pub fn new_simple(v: u8) -> Self {
        Self::new([v; 6])
    }

    pub fn new_upright(top: u8, side: u8, bottom: u8) -> Self {
        Self::new([side, side, top, bottom, side, side])
    }

    pub fn get_dir(&self, dir: Direction) -> u8 {
        self.indexes[dir.to_index()]
    }
}
